
#ifndef ARP_H
#define ARP_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>
#include <net/if.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
//#include <linux/if_ether.h>
#include <linux/if_packet.h>
//#include <linux/in.h>
#include <linux/if_ether.h>

#include "Utility.h"

#define ETH_HW_ADDR_LEN 6
#define IP_ADDR_LEN 4
#define ARP_FRAME_TYPE 0x0806
#define ETHER_HW_TYPE 1
#define IP_PROTO_TYPE 0x0800
#define OP_ARP_REQUEST 2

#define DEFAULT_DEVICE "wlan0"

#define NUM_ECHO_REQUESTS 4
#define ECHO_TIMEOUT 3
#define ECHO_INTERVAL 1
/*
struct arp_packet
{
	unsigned char targ_hw_addr[ETH_HW_ADDR_LEN];
	unsigned char src_hw_addr[ETH_HW_ADDR_LEN];
	unsigned short frame_type;
	unsigned short hw_type;
	unsigned short prot_type;
	unsigned char hw_addr_size;
	unsigned char prot_addr_size;
	unsigned short op;
	unsigned char sndr_hw_addr[ETH_HW_ADDR_LEN];
	unsigned char sndr_ip_addr[IP_ADDR_LEN];
	unsigned char rcpt_hw_addr[ETH_HW_ADDR_LEN];
	unsigned char rcpt_ip_addr[IP_ADDR_LEN];
	unsigned char padding[18];
};
*/
struct arp_packet
{
	struct ethhdr ethd;
	struct arphdr arphd;
	unsigned char eth_src_addr[6];
	//unsigned char inet_src_addr[4];
	unsigned long inet_src_addr;
	unsigned char eth_dst_addr[2];
	unsigned long inet_dst_addr;
	unsigned char padding[18];
}__attribute__((packed));

void die(const char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}

void get_ip_addr(struct in_addr* in_addr,char* str){

struct hostent *hostp;

in_addr->s_addr=inet_addr(str);
if(in_addr->s_addr == -1){
        if( (hostp = gethostbyname(str)))
                bcopy(hostp->h_addr,in_addr,hostp->h_length);
        else {
                fprintf(stderr,"send_arp: unknown host %s\n",str);
                exit(1);
                }
        }
}

void get_hw_addr(unsigned char* buf,char* str){

int i;
char c,val;

for(i=0;i<ETH_HW_ADDR_LEN;i++){
        if( !(c = tolower(*str++))) die("Invalid hardware address");
        if(isdigit(c)) val = c-'0';
        else if(c >= 'a' && c <= 'f') val = c-'a'+10;
        else die("Invalid hardware address");

        *buf = val << 4;
        if( !(c = tolower(*str++))) die("Invalid hardware address");
        if(isdigit(c)) val = c-'0';
        else if(c >= 'a' && c <= 'f') val = c-'a'+10;
        else die("Invalid hardware address");

        *buf++ |= val;

        if(*str == ':')str++;
        }
}


unsigned char* CreatArpPacket(std::string& srcAddr, std::string& dstAddr)
{
	static struct arp_packet packet;
	memset(packet.ethd.h_dest,0xff, ETH_ALEN);
	unsigned char my_MAC[6];
	GetSourceMAC(my_MAC);
	memcpy(packet.ethd.h_source,my_MAC,ETH_ALEN);
	packet.ethd.h_proto = htons(ETH_P_ARP);
	memset(&(packet.arphd),0,sizeof(struct arphdr));
	packet.arphd.ar_hrd = htons(ARPHRD_ETHER);
	packet.arphd.ar_pro = htons(ETH_P_IP);
	packet.arphd.ar_hln = ETH_ALEN;
	packet.arphd.ar_pln = 4;
	packet.arphd.ar_op = htons(ARPOP_REQUEST);

	memcpy(packet.eth_src_addr,my_MAC,6);
	
	packet.inet_src_addr = inet_addr(srcAddr.c_str());
	memset(packet.eth_dst_addr,0,6);
	
	packet.inet_dst_addr = inet_addr(dstAddr.c_str());

	memset(packet.padding,0,18);	
	return (unsigned char*)&packet;
}

void print_data(unsigned char *buf)
{
	int i;
	if(buf != NULL)
	{
		for (i = 0; i < 60 ; i ++)
		{
			if (i!= 0 && i % 16 == 0)
				std::cout << std::endl;
			fprintf(stdout, "%02X",buf[i]);
		}
		std::cout << std::endl;
	}
}

void print_ARPMsg(struct arp_packet *packet)
{
	unsigned char *mac;
	struct in_addr ip;
	printf("Source MAC\t\tSource IP\t\t  Destinate IP\n");
	mac = packet->eth_src_addr;
	ip.s_addr = packet->inet_src_addr;
	printf("%.2X:%.2X:%.2X:%.2X:%.2X:%.2X\t",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
	printf("%s\t\t",inet_ntoa(ip));
	
	mac = packet->eth_dst_addr;
	ip.s_addr = packet->inet_dst_addr;
	//printf("%.2X:%.2X:%.2X:%.2X:%.2X:%.2X\t",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
	printf("%s\t\t\n",inet_ntoa(ip));
}

int Arp(std::string dstAddr)
{
	

//////////////My Code/////////////////////////////////////////////
	int sockfd;
	struct sockaddr_ll my_etheraddr;
	unsigned char *pktbuf;
	unsigned char ackbuf[80];
	
	int selectVal;
	struct timeval time_out;
	fd_set readset, writeset;

	std::string srcAddr;
	//get my IP address
	GetSourceIP(srcAddr);
	
	memset(&pktbuf, 0 ,sizeof(pktbuf));
	//set Arp packet:
	pktbuf = CreatArpPacket(srcAddr, dstAddr);

	std::cout << "Destination address: " << dstAddr << std::endl;
	std::cout << "Source address: " << srcAddr << std::endl;

	memset(&my_etheraddr,0,sizeof(my_etheraddr));
	
	my_etheraddr.sll_family = PF_PACKET;
	my_etheraddr.sll_protocol = htons(ETH_P_IP);
	my_etheraddr.sll_ifindex = get_ifindex();
	
	
///////////go!//////////
	//create socket
	sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
	if(sockfd < 0)
	{
		perror("socket");
		exit(1);
	}
	//set broadcast
	const int const_int_1 = 1;
	if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &const_int_1, sizeof(const_int_1)) == -1)
	{
		perror("setsockopt");
		exit(1);
	}

	printf("Send ARP packet:\n");
	//print_data(pktbuf);
	print_ARPMsg((struct arp_packet*)pktbuf);
	
	if (sendto(sockfd, pktbuf, 80, 0, (struct sockaddr*)&my_etheraddr, sizeof(my_etheraddr))<=0)
	{
		perror("sendto");
		exit(-1);
	}
	
	
	//receive ARP packet
	time_out.tv_sec = 6;
	time_out.tv_usec = 0;
	
	FD_ZERO(&readset);
	FD_ZERO(&writeset);
	FD_SET(sockfd, &readset);
	selectVal = select(sockfd+1,&readset,&writeset,NULL,&time_out);
	if(selectVal == -1)
	{
		perror("select");
		close(sockfd);
		return -1;
	}
	if(selectVal == 0)
	{
		std::cout << "Timeout occured for attempt."<< std::endl;
		return -2;
	}
	
	int recv = recvfrom(sockfd, ackbuf, 80 ,0, NULL, 0 );
	if (recv == -1)
	{
		perror("recv");
		return -1;
	}
	else
	{
		//decrypt the ARP response
		std::cout<<"receive response!"<<std::endl;
		print_ARPMsg((struct arp_packet*)ackbuf);
		close(sockfd);
		return 0;	
	}
/////////////End of my code///////////////////////////////////////

}



#endif // ARP_H
